/**
 * Copyright (c) 2008, Fabien Antoine
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

package fas.massload.file;

import fas.massload.events.MassLoaderEvent;
import fas.massload.events.MassLoaderErrorEvent;
import fas.massload.file.ILoadFile;

import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.events.Event;
import flash.events.ProgressEvent;

class GLoadFile extends EventDispatcher, implements ILoadFile
{
	//---------//
	//Variables//
	//---------//
	
	private var _files:Array<ILoadFile>;
	private var _completed:UInt;
	private var _startedFiles:Array<ILoadFile>;
	
	
	// ----------- //
	// Constructor //
	// ----------- //
	
	/**
	 * Constructor of GLoadFile
	 */
	public function new( aFiles:Array<ILoadFile>, sName:String )
	{
		super();
		
		// Save the properties
		_files = aFiles;
		_completed = 0;
		_startedFiles = new Array<ILoadFile>();
		
		name  	= sName;
		type 		= FileType.GroupLoader;
		loading	= false;
	}
	
	
	
	// -------------- //
	// Public methods //
	// -------------- //
	
	/**
	 * Start the loading of the group
	 */
	public function load():Void
	{
		if( !loading && _files.length > 0 )
		{
			loading = true;
			
			_completed = 0;
			
			_startedFiles = new Array<ILoadFile>();
			
			// Launch loading of all files
			var i:Int;
			for( i in 0..._files.length )
			{
				_startedFiles.push( _files[ i ] );
				startElem( _files[ i ] );
			}
		}
	}
	
	/**
	 * Stop the loading of the group
	 */
	public function close( ?error:Bool=false ):Void
	{
		if( loading )
		{
			loading = false;
			
			// Stop loading of all files
			var i:Int;
			for( i in 0..._files.length )
			{
				stopElem( _files[ i ] );
			}
			
			// Dispatch close event
			if( error ) dispatchEvent( new MassLoaderEvent( MassLoaderEvent.CLOSE, this ) );
		}
	}
	
	/**
	 * String version of the LoadFile
	 * @return				String			String version of the LoadFile
	 */
	public override function toString():String
	{
		var back:String = "<GLoadFile>" + name + "{";
		
		if( _files.length > 0 )
		{
			var tab:Array<String> = new Array<String>();
			for( i in 0..._files.length ) tab.push( _files[ i ].toString() );
			back += tab.join( "," );
		}else back += "*EMPTY*";
		
		return back + "}";
	}
	
	
	// --------------- //
	// Private methods //
	// --------------- //
	
	/**
	 * Register events and start loading of an element
	 * @param	elem		ILoadFile		File to start download
	 */
	private function startElem( elem:ILoadFile ):Void
	{
		// Register events
		elem.addEventListener( MassLoaderEvent.COMPLETE, onElemComplete );
		elem.addEventListener( MassLoaderEvent.PROGRESS, onProgress );
		elem.addEventListener( MassLoaderErrorEvent.IO_ERROR, onIOError );
		elem.addEventListener( MassLoaderErrorEvent.SECURITY_ERROR, onSecurityError );
		
		// Start loading
		elem.load();
	}
	
	/**
	 * Unregister events and stop loading of an element
	 * @param	elem		ILoadFile		File to stop download
	 */
	private function stopElem( elem:ILoadFile ):Void
	{
		// Stop loading and catching errors
		try
		{
			elem.close();
		}catch( e:Dynamic ) { }
		
		// Unregister events
		elem.removeEventListener( MassLoaderEvent.COMPLETE, onElemComplete );
		elem.removeEventListener( MassLoaderEvent.PROGRESS, onProgress );
		elem.removeEventListener( MassLoaderErrorEvent.IO_ERROR, onIOError );
		elem.removeEventListener( MassLoaderErrorEvent.SECURITY_ERROR, onSecurityError );
	}
	
	
	// ------ //
	// Events //
	// ------ //
	
	/**
	 * Event called when io error
	 * @param	ev			MassLoaderErrorEvent			Event received
	 */
	private function onIOError( ev:MassLoaderErrorEvent ):Void
	{
		// We stop all downloads
		close( true );
		
		// Dispatch error events
		dispatchEvent( new MassLoaderErrorEvent( MassLoaderErrorEvent.IO_ERROR, this, ev.text ) );
		dispatchEvent( new MassLoaderErrorEvent( MassLoaderErrorEvent.ERROR, this, ev.text ) );
	}
	
	/**
	 * Event called when io error
	 * @param	ev			MassLoaderErrorEvent		Event received
	 */
	private function onSecurityError( ev:MassLoaderErrorEvent ):Void
	{
		// We stop all downloads
		close( true );
		
		// Dispatch error events
		dispatchEvent( new MassLoaderErrorEvent( MassLoaderErrorEvent.SECURITY_ERROR, this, ev.text ) );
		dispatchEvent( new MassLoaderErrorEvent( MassLoaderErrorEvent.ERROR, this, ev.text ) );
	}
	
	/**
	 * Event called when complete loading
	 */
	private function onComplete():Void
	{
		// Dispatch the event
		dispatchEvent( new MassLoaderEvent( MassLoaderEvent.COMPLETE, this ) );
		
		// Close the loader
		close();
	}
	
	
	/**
	 * Event called when progress loading
	 * @param	ev			MassLoaderEvent			Event received
	 */
	private function onProgress( ev:MassLoaderEvent ):Void
	{
		// If it's the first progress event send by the file, we remember the file
		if( _startedFiles.length > 0 ) _startedFiles.remove( ev.file );
		
		// If all files have sent a progress event, we can dispatch nexts progress events
		if( _startedFiles.length == 0 )
		{
			// Calcul new bytes
			var bLoaded:UInt = 0;
			var bTotal:UInt = 0;
			
			for( i in 0..._files.length )
			{
				bLoaded += _files[ i ].bytesLoaded;
				bTotal += _files[ i ].bytesTotal;
			}
			
			// Dispatch the event
			dispatchEvent( new MassLoaderEvent( MassLoaderEvent.PROGRESS, this, bLoaded, bTotal ) );
		}
	}
	
	/**
	 * Event called when an element is completly loaded
	 * @param	ev			Event					Event received
	 */
	private function onElemComplete( ev:Event ):Void
	{
		// Increase the number of total loaded
		_completed++;
		
		var file:ILoadFile = cast( ev.target, ILoadFile );
		
		// make sure that the element is noticed as started
		_startedFiles.remove( file );
		
		// Dispatch the COMPLETE_ELEM event
		dispatchEvent( new MassLoaderEvent( MassLoaderEvent.COMPLETE_ELEM, this, file.bytesLoaded, file.bytesTotal, file ) );
		
		// If this number equal the number of files to download we call the COMPLETE event
		if( _completed == _files.length ) onComplete();
	}		
	
	
	// --------- //
	// Accessors //
	// --------- //
	
	/**
	 * State of loading
	 */
	public var loading( default, null ):Bool;
	
	/**
	 * Type of the LoadFile: GroupLoader
	 */
	public var type( default, null ):FileType;
	
	/**
	 * Name of the LoadFile
	 */
	public var name( default, default ):String;
	
	/**
	 * Number of bytes loaded for the LoadFile
	 */
	public var bytesLoaded( getBytesLoaded, null ):UInt;
	private function getBytesLoaded():UInt
	{
		// Calcul new bytes
		var bLoaded:UInt = 0;
		for( i in 0..._files.length ) bLoaded += _files[ i ].bytesLoaded;
		
		return bLoaded;
	}
	
	
	/**
	 * Total number of bytes of the LoadFile
	 */
	public var bytesTotal( getBytesTotal, null ):UInt;
	private function getBytesTotal():UInt
	{
		// Calcul new bytes
		var bTotal:UInt = 0;
		for( i in 0..._files.length ) bTotal += _files[ i ].bytesTotal;
		
		return bTotal;
	}
	
	/**
	 * Data received from the file
	 */
	public var content( getContent, null ):Dynamic;
	private function getContent():Dynamic
	{
		var aBack:Hash<ILoadFile> = new Hash<ILoadFile>();
		
		// Saves all the files in an array
		for( i in 0..._files.length ) aBack.set( _files[ i ].name, _files[ i ] );
		
		return aBack;
	}
}
